#include <stdio.h>
#include <string.h>
#include "base/core/com_define.h"

#if WIN32
#define NO_QFORKIMPL
#include "win32fixes.h"
#endif

#include "DBOption/Redis/RedisConnector.h"
#include "base/core/os/debug.h"
#include "base/core/os/time.h"
#include "base/core/char_operation.h"
#include "base/core/Utf8AndWideChar.h"

RedisConnector::RedisConnector()
	: m_port(0)
	,m_password(nullptr)
	,m_context(nullptr) 
{
    memset(m_ip, 0, sizeof(m_ip));
}

RedisConnector::~RedisConnector() 
{
    MMO_DELETE_ARRAY(m_password);
	Close();
}

bool RedisConnector::Connect(const char* ip, uint32_t port, const char* password) 
{
    bool result  = false;
    bool retCode = false;

    MMO_ASSERT(ip != nullptr);
    m_port = port;
    if (password != nullptr) 
	{
        int len = (int)strlen(password) + 1;
        m_password = new char[len];
        MMOLOG_ASSERT_EXIT(m_password != nullptr);
        SafeCopyString(m_password, len, password);
    }
    retCode = SafeCopyString(m_ip, sizeof(m_ip), ip);
    MMOLOG_PROCESS_ERROR(retCode);

    retCode = _Connect();
    MMOLOG_PROCESS_ERROR(retCode);
    result = true;
Exit0:
    return result;
}

bool RedisConnector::Close() 
{
    if (m_context != nullptr) 
	{
        redisFree(m_context);
        m_context = nullptr;
    }
    return true;
}

redisReply* RedisConnector::Execute(const char* format, ...) 
{
    bool retCode = false;
    va_list ap;
    redisReply* reply = nullptr;
    
    MMOLOG_ASSERT_EXIT(format != nullptr);
    MMOLOG_ASSERT_EXIT(m_context != nullptr);

    va_start(ap, format);
    reply = (redisReply*)redisvCommand(m_context, format, ap);
    va_end(ap);

    if (reply == nullptr) 
	{
        retCode = _Connect();
        MMOLOG_PROCESS_ERROR(retCode);

        va_start(ap, format);
        reply = (redisReply*)redisvCommand(m_context, format, ap);
        va_end(ap);
    }

Exit0:
    return reply;
}

redisReply* RedisConnector::_ExecuteVaList(const char* format, va_list ap) 
{
    bool retCode = false;
    va_list apCopy;
    redisReply* reply = nullptr;

    MMOLOG_ASSERT_EXIT(format != nullptr);
    MMOLOG_ASSERT_EXIT(m_context != nullptr);
    va_copy(apCopy, ap);
    reply = (redisReply*)redisvCommand(m_context, format, ap);
    if (reply == nullptr) 
	{
        retCode = _Connect();
		if (!retCode)
		{
			MMOLOG_PROCESS_ERROR(retCode);
			va_end(apCopy);
		}
        reply = (redisReply*)redisvCommand(m_context, format, apCopy);
    }
	va_end(apCopy);
Exit0:
    return reply;
}

bool RedisConnector::AppendCommand(const char* format, ...) 
{
    MMO_ASSERT(format);
    MMO_ASSERT(m_context);
    va_list ap;
    va_start(ap, format);
    int ret = redisvAppendCommand(m_context, format, ap);
    va_end(ap);
    return (ret == REDIS_OK);
}

redisReply* RedisConnector::GetReply() 
{
    bool retCode = false;
    int  retRedisCode = -1;
    redisReply* reply = nullptr;
    retRedisCode = redisGetReply(m_context, (void**)&reply);
    if (retRedisCode != REDIS_OK) 
	{
        LogErrFmtPrint("[redis] redisGetReply Fail!");
    }
    // Try
    if (reply == nullptr) 
	{
        retCode = _Connect();
        MMOLOG_PROCESS_ERROR(retCode);
    }
Exit0:
    return reply;
}

bool RedisConnector::_Connect() 
{
    struct timeval timeOut;
    timeOut.tv_sec = 0;
    timeOut.tv_usec = 500;
    int tryTimes = 0;

    Close();
    do 
	{
        m_context = redisConnectWithTimeout(m_ip, m_port, timeOut);
        if (m_context != nullptr && m_context->err == 0) 
		{
            if (_AuthPassword()) 
			{
                LogInfoFmtPrint("connect redis %s:%d success", m_ip, m_port);
                return true;
            }
        }
        if (m_context != nullptr && tryTimes % 10 == 0) 
		{
#ifdef WIN32
            char utf8[countof(m_context->errstr)];
            GBKToUtf8(utf8, countof(utf8), m_context->errstr, strlen(m_context->errstr));
            LogErrFmtPrint("connect redis %s:%d fail(%s), try reconnect...", m_ip, m_port, utf8);
#else
            LogErrFmtPrint("connect redis %s:%d fail(%s), try reconnect...", m_ip, m_port, m_context->errstr);
#endif
        }
        if (m_context != nullptr) 
		{
            redisFree(m_context);
            m_context = nullptr;
        }
        ++tryTimes;
        Time::Sleep(200);
    } while (tryTimes < 1000);

    LogErrFmtPrint("connect redis %s:%d fail", m_ip, m_port);
    return false;
}

bool RedisConnector::_AuthPassword() 
{
    bool retCode = false;
    redisReply* reply = nullptr;

    MMO_PROCESS_SUCCESS(m_password == nullptr || m_password[0] == 0);

    reply= (redisReply*)redisCommand(m_context, "AUTH %s", m_password);
    MMOLOG_PROCESS_ERROR(!(reply == nullptr || reply->type == REDIS_REPLY_NIL));
    MMOLOG_PROCESS_ERROR(reply->type == REDIS_REPLY_STATUS);
    MMOLOG_PROCESS_ERROR(reply->len == 2);
    MMOLOG_PROCESS_ERROR(strcmp(reply->str, "OK") == 0);
Exit1:
    retCode = true;
Exit0:
    if (reply != nullptr) {
        freeReplyObject(reply);
    }
    return retCode;
}
